#include "AudioOutput.h"
#include "ByteStream.h"
#include <QThread>
#include <QDateTime>

#include <cstring>
#include <windows.h>
#include <mmdeviceapi.h>
#include <Audioclient.h>

#include <QDebug>




static QString DebugErrOut(const char* errTag,HRESULT res){

  QString err(errTag);
  if(res!=0){
    err+=QString::number((unsigned long)res,16);
  }
  qDebug().noquote()<<err<<Qt::endl;

  return err;
}




class AudioDestroyThread:public QThread{
  public:
    AudioDestroyThread(int StreamBufferSize,int Size,int Interval);
    ~AudioDestroyThread();

    void Stop();
    void SetParam(int Size,int Interval);
    void SetStream(int StreamBufferSize);
    void Input(unsigned char* Data,int Length);

  protected:
    void run();
  private:
    HANDLE StopEvent;
    std::recursive_mutex* Lock;
    std::recursive_mutex* StreamWriteLock;
    ByteStream* Stream;
    int Size;
    int Interval;
    unsigned char* Buffer;
    
    int GetNowTime();
};

AudioDestroyThread::AudioDestroyThread(int StreamBufferSize,int Size,int Interval)
{

  this->StopEvent=CreateEvent(nullptr,false,false,nullptr);
  this->Lock=new std::recursive_mutex();
  this->StreamWriteLock=new std::recursive_mutex();
  this->Stream=new ByteStream(StreamBufferSize);
  this->Size=Size;
  this->Interval=Interval;
  this->Buffer=new unsigned char[Size];
  
}

AudioDestroyThread::~AudioDestroyThread()
{
  delete[] Buffer;
  delete Lock;
  delete StreamWriteLock;
  CloseHandle(StopEvent);
  delete this->Stream;
}

void AudioDestroyThread::Stop(){
  SetEvent(StopEvent);
}

void AudioDestroyThread::SetParam(int Size,int Interval){
  Lock->lock();

  this->Size=Size;
  this->Interval=Interval;
  delete[] this->Buffer;
  this->Buffer=new unsigned char[Size];

  Lock->unlock();
}

void AudioDestroyThread::SetStream(int StreamBufferSize){
  StreamWriteLock->lock();
  Lock->lock();

  delete this->Stream;
  this->Stream=new ByteStream(StreamBufferSize);

  Lock->unlock();
  StreamWriteLock->unlock();
}

void AudioDestroyThread::Input(unsigned char* Data,int Length){
  StreamWriteLock->lock();

  this->Stream->Write(Data,Length);

  StreamWriteLock->unlock();
}

int AudioDestroyThread::GetNowTime(){
  return QDateTime::currentMSecsSinceEpoch();
}

void AudioDestroyThread::run()
{  
  int Size;
  int Interval;
  unsigned char* Buffer;

  int LastTime;


  LastTime=GetNowTime();

  for(;;){
    if(WAIT_OBJECT_0==WaitForSingleObject(StopEvent,0)){
      break;
    }

    Lock->lock();

    Size=this->Size;
    Interval=this->Interval;
    Buffer=this->Buffer;
    
    Stream->TryRead(Buffer,Size);

    Lock->unlock();

    while(((int)(GetNowTime()-LastTime))<Interval){
      QThread::msleep(2);
    }
    LastTime+=Interval;

  }
  
}




typedef struct{
  IAudioRenderClient* AudioRenderClient;
  IAudioClient* AudioClient;

  volatile bool IsStop;
  ByteStream* Stream;
  void (*ErrorCallback)(void*,const char*);
  void* v;
}AudioOutput_Handle;




class AudioOutputThread:public QThread{
  public:
    AudioOutputThread(AudioOutput_Handle* audioOutput_Handle,int FrameSize,int BufferSize,int BlockCnt);
    ~AudioOutputThread();

    void Stop();

  protected:
    void run();
  private:
    HANDLE StopEvent;
    AudioOutput_Handle* audioOutput_Handle;
    int FrameSize;
    int BufferSize;
    unsigned char* Buffer;
    int BlockCnt;
    unsigned char* EmptyBuffer;

    void ErrorExecute();
};

AudioOutputThread::AudioOutputThread(AudioOutput_Handle* audioOutput_Handle,int FrameSize,int BufferSize,int BlockCnt)
{

  this->StopEvent=CreateEvent(nullptr,false,false,nullptr);

  this->audioOutput_Handle=audioOutput_Handle;
  this->FrameSize=FrameSize;
  this->BufferSize=BufferSize;
  this->Buffer=new unsigned char[BufferSize/BlockCnt];
  this->BlockCnt=BlockCnt;
  this->EmptyBuffer=new unsigned char[BufferSize/BlockCnt];

  memset(this->Buffer,0,BufferSize/BlockCnt);
  memset(this->EmptyBuffer,0,BufferSize/BlockCnt);
}

AudioOutputThread::~AudioOutputThread(){
  delete[] Buffer;
  delete[] EmptyBuffer;
  CloseHandle(StopEvent);
}

void AudioOutputThread::Stop(){
  SetEvent(StopEvent);
}

void AudioOutputThread::ErrorExecute(){

  audioOutput_Handle->IsStop=true;

  while(0==audioOutput_Handle->Stream->TryRead(Buffer,BufferSize/BlockCnt)){
  }
  
}

void AudioOutputThread::run(){
  IAudioClient* AudioClient;
  IAudioRenderClient* AudioRenderClient;
  HRESULT res;

  UINT32 NumBufferFrames;
  UINT32 NumPaddingFrames;
  UINT32 NumEmptyFrames;
  BYTE* pData;
  DWORD dwFlags;


  AudioClient=audioOutput_Handle->AudioClient;
  AudioRenderClient=audioOutput_Handle->AudioRenderClient;


  res=AudioClient->GetBufferSize(&NumBufferFrames);
  if(res!=S_OK){
    ErrorExecute();
    QString str=DebugErrOut("GetBufferSize()错误:",res);
    audioOutput_Handle->ErrorCallback(audioOutput_Handle->v,str.toUtf8().data());
    return;
  }

  res=AudioRenderClient->GetBuffer(NumBufferFrames,&pData);
  if(res!=S_OK){
    ErrorExecute();
    QString str=DebugErrOut("GetBuffer()错误:",res);
    audioOutput_Handle->ErrorCallback(audioOutput_Handle->v,str.toUtf8().data());
    return;
  }

  res=AudioRenderClient->ReleaseBuffer(NumBufferFrames,AUDCLNT_BUFFERFLAGS_SILENT);
  if(res!=S_OK){
    ErrorExecute();
    QString str=DebugErrOut("ReleaseBuffer()错误:",res);
    audioOutput_Handle->ErrorCallback(audioOutput_Handle->v,str.toUtf8().data());
    return;
  }


  AudioClient->Start();

  for(;;){
    if(WAIT_OBJECT_0==WaitForSingleObject(StopEvent,0)){
      break;
    }

    res=AudioClient->GetCurrentPadding(&NumPaddingFrames);
    if(res!=S_OK){
      ErrorExecute();
      AudioClient->Stop();
      QString str=DebugErrOut("GetCurrentPadding()错误:",res);
      audioOutput_Handle->ErrorCallback(audioOutput_Handle->v,str.toUtf8().data());
      return;
    }

    NumEmptyFrames=NumBufferFrames-NumPaddingFrames;
    
    res=AudioRenderClient->GetBuffer(NumEmptyFrames,&pData);
    if(res!=S_OK){
      if(res==AUDCLNT_E_BUFFER_TOO_LARGE){
        DebugErrOut("GetBuffer()错误:",res);
        continue;
      }else{
        ErrorExecute();
        AudioClient->Stop();
        QString str=DebugErrOut("GetBuffer()错误:",res);
        audioOutput_Handle->ErrorCallback(audioOutput_Handle->v,str.toUtf8().data());
        return;
      }
    }

    if(0==audioOutput_Handle->Stream->TryRead(pData,NumEmptyFrames*FrameSize)){
      dwFlags=0U;
    }else{
      dwFlags=AUDCLNT_BUFFERFLAGS_SILENT;
    }

    res=AudioRenderClient->ReleaseBuffer(NumEmptyFrames,dwFlags);
    if(res!=S_OK){
      ErrorExecute();
      AudioClient->Stop();
      QString str=DebugErrOut("ReleaseBuffer()错误:",res);
      audioOutput_Handle->ErrorCallback(audioOutput_Handle->v,str.toUtf8().data());
      return;
    }

    QThread::msleep(2);

  }
  
  DebugErrOut("音频线程正常退出",0);
}




AudioOutput::AudioOutput(int SampleRate,int NumOfSampleBit){

  this->DeviceHandle=nullptr;
  this->DeviceHandleLock=new std::recursive_mutex();

  this->BufferSize=1024*8;

  this->SampleRate=SampleRate;
  this->NumOfSampleBit=NumOfSampleBit;

  this->DestroyThread=new AudioDestroyThread(1024*8*4,192,2);

  ((AudioDestroyThread*)this->DestroyThread)->start();

}

AudioOutput::~AudioOutput(){
  
  if(DeviceHandle!=nullptr){
    Close();
  }
  
  delete (std::recursive_mutex*)DeviceHandleLock;

  ((AudioDestroyThread*)DestroyThread)->Stop();
  ((AudioDestroyThread*)DestroyThread)->wait();
  delete (AudioDestroyThread*)DestroyThread;
}

int AudioOutput::SetSampleParam(int SampleRate,int NumOfSampleBit){
  this->SampleRate=SampleRate;
  this->NumOfSampleBit=NumOfSampleBit;
  DestroyThreadUpDate();
  return 0;
}

int AudioOutput::SetBuffer(int BufferSize,int BlockCount){
  this->BufferSize=BufferSize;
  this->BlockCount=BlockCount;
  DestroyThreadUpDate();
  return 0;
}

int AudioOutput::Open(void (*ErrorCallback)(void*,const char*),void* v){
  int r;
  ((std::recursive_mutex*)DeviceHandleLock)->lock();

  if(DeviceHandle!=nullptr){
    r=-1;
  }else{
    r=TryOpen(ErrorCallback,v);
  }

  ((std::recursive_mutex*)DeviceHandleLock)->unlock();
  return r;
}

void AudioOutput::Close(){

  ((std::recursive_mutex*)DeviceHandleLock)->lock();

  if(DeviceHandle!=nullptr){

    ((AudioOutputThread*)this->Thread)->Stop();
    ((AudioOutputThread*)this->Thread)->wait();

    delete (AudioOutputThread*)this->Thread;

    ((AudioOutput_Handle*)DeviceHandle)->AudioClient->Release();
    ((AudioOutput_Handle*)DeviceHandle)->AudioRenderClient->Release();

    delete ((AudioOutput_Handle*)DeviceHandle)->Stream;
    delete (AudioOutput_Handle*)DeviceHandle;
    DeviceHandle=nullptr;
  }
  ((std::recursive_mutex*)DeviceHandleLock)->unlock();

}

void AudioOutput::Input(void* Data,int Length){

  if(((std::recursive_mutex*)DeviceHandleLock)->try_lock()){

    if(DeviceHandle!=nullptr){

      if(!((AudioOutput_Handle*)DeviceHandle)->IsStop){

        ((AudioOutput_Handle*)DeviceHandle)->Stream->Write((unsigned char*)Data,Length);

        ((std::recursive_mutex*)DeviceHandleLock)->unlock();
        return;
      }
    }

    ((std::recursive_mutex*)DeviceHandleLock)->unlock();
  }

  ((AudioDestroyThread*)DestroyThread)->Input((unsigned char*)Data,Length);

}

void AudioOutput::DestroyThreadUpDate(){

  ((AudioDestroyThread*)this->DestroyThread)->SetStream(BufferSize*2*NumOfSampleBit/8);  
  ((AudioDestroyThread*)this->DestroyThread)->SetParam(SampleRate*2*NumOfSampleBit/8/250,4);
  
}

int AudioOutput::TryOpen(void (*ErrorCallback)(void*,const char*),void* v){
  int r;
  AudioOutput_Handle* deviceHandle;
  HRESULT res;
  WAVEFORMATEX format;

  const CLSID CLSID_MMDeviceEnumerator=__uuidof(MMDeviceEnumerator);
  const IID IID_IMMDeviceEnumerator=__uuidof(IMMDeviceEnumerator);
  const IID IID_IAudioClient=__uuidof(IAudioClient);
  const IID IID_IAudioRenderClient=__uuidof(IAudioRenderClient);
  IMMDeviceEnumerator* enumerator=nullptr;
  IMMDevice* Device=nullptr;


  deviceHandle=new AudioOutput_Handle;

  deviceHandle->AudioRenderClient=nullptr;
  deviceHandle->AudioClient=nullptr;

  deviceHandle->IsStop=false;
  deviceHandle->ErrorCallback=ErrorCallback;
  deviceHandle->v=v;


  try{
    
    format.wFormatTag=WAVE_FORMAT_PCM;
    format.nChannels=2;
    format.nSamplesPerSec=SampleRate;
    format.nAvgBytesPerSec=SampleRate*NumOfSampleBit*2/8;
    format.nBlockAlign=NumOfSampleBit*2/8;
    format.wBitsPerSample=NumOfSampleBit;
    format.cbSize=0;

    deviceHandle->Stream=new ByteStream(BufferSize*format.nBlockAlign);

    res=CoCreateInstance(CLSID_MMDeviceEnumerator,nullptr,CLSCTX_ALL,IID_IMMDeviceEnumerator,(void**)&enumerator);
    if(res!=S_OK){
      DebugErrOut("CoCreateInstance()错误:",res);
      throw -1;
    }

    res=enumerator->GetDefaultAudioEndpoint(EDataFlow::eRender,eConsole,&Device);
    if(res!=S_OK){
      DebugErrOut("GetDefaultAudioEndpoint()错误:",res);
      throw -1;
    }

    res=Device->Activate(IID_IAudioClient,CLSCTX_ALL,nullptr,(void**)&deviceHandle->AudioClient);
    if(res!=S_OK){
      DebugErrOut("Activate()错误:",res);
      throw -1;
    }

    REFERENCE_TIME hnsDefaultDevicePeriod;
    REFERENCE_TIME hnsMinimumDevicePeriod;
    res=deviceHandle->AudioClient->GetDevicePeriod(&hnsDefaultDevicePeriod,&hnsMinimumDevicePeriod);
    if(res!=S_OK){
      DebugErrOut("GetDevicePeriod()错误:",res);
      throw -1;
    }
    
    REFERENCE_TIME hnsBufferDuration;

    if(hnsDefaultDevicePeriod<8*10000){
      hnsBufferDuration=8*10000;
    }else{
      hnsBufferDuration=hnsDefaultDevicePeriod;
    }

    res=deviceHandle->AudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED,AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM|AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY,hnsBufferDuration,0,&format,nullptr);
    if(res!=S_OK){
      DebugErrOut("Initialize()错误:",res);
      throw -1;
    }

    res=deviceHandle->AudioClient->GetService(IID_IAudioRenderClient,(void**)&deviceHandle->AudioRenderClient);
    if(res!=S_OK){
      DebugErrOut("GetService()错误:",res);
      throw -1;
    }
    



    DeviceHandle=deviceHandle;
    
    this->Thread=new AudioOutputThread(deviceHandle,format.nBlockAlign,BufferSize*format.nBlockAlign,BlockCount);
    ((AudioOutputThread*)this->Thread)->start(QThread::HighPriority);


    r=0;
  }catch(...){

    if(deviceHandle->AudioClient!=nullptr){
      deviceHandle->AudioClient->Release();
    }
    if(deviceHandle->AudioRenderClient!=nullptr){
      deviceHandle->AudioRenderClient->Release();
    }

    delete deviceHandle->Stream;

    delete deviceHandle;

    r=-1;
  }

  
  if(Device!=nullptr){
    Device->Release();
  }
  if(enumerator!=nullptr){
    enumerator->Release();
  }
  return r;
}



